﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JGLib.States;
using Microsoft.Xna.Framework.Graphics;
using TrafficTrouble.Core;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input.Touch;

namespace TrafficTrouble.States
{
    public class LevelSelectionState : State
    {
        private const int NUM_COLUMNS = 4;
        private const float BUTTON_MARGIN = 20f;
        private const float VELOCITY_MULTIPLIER = .95f;
        private float ButtonSize { get { return this.GetTexture(Content.Button).Height; } }
        private float HeadingHeight { get { return this.GetTexture(Content.Heading).Height; } }
        private const float TAP_TO_DRAG_DISTANCE = 10f;

        private GraphicsDevice GraphicsDevice { get; set; }

        enum Content { Background, Button, ThumbnailOverlay, Heading, NumberFont }

        private LevelDifficulty CurrentDifficulty { get; set; }

        private Texture2D[][] Thumbnails { get; set; }

        private float YVelocity = 0;
        private float YPosition = 0;
        private float[] MaxYPositions;

        private LevelDifficulty HighestDifficultyLoaded = LevelDifficulty.Beginner;
        private int HighestLevelNumberLoaded = 1;
        private bool DoneLoading = false;

        private int LevelNumberTouched = -1;

        private float XOffset = 0;
        bool ScrollingHorizontally = false;

        public LevelSelectionState(GraphicsDevice device)
            : base(typeof(LevelSelectionState))
        {
            this.CurrentDifficulty = LevelDifficulty.Beginner;

            this.Thumbnails = new Texture2D[Difficulty.NumDifficulties][];
            for (int i = 0; i < Difficulty.NumDifficulties; i++)
                this.Thumbnails[i] = new Texture2D[Grid.GetNumLevelsOfDifficulty((LevelDifficulty)i)];
            this.GraphicsDevice = device;
        }

        protected override void Initialize()
        {
            this.MaxYPositions = new float[Difficulty.NumDifficulties];
            for (int i = 0; i < (int)LevelDifficulty.Master; i++)
            {
                int numRows = (int)Math.Ceiling((double)Grid.GetNumLevelsOfDifficulty((LevelDifficulty)i) / NUM_COLUMNS);
                float scrollAreaHeight = Game.Height - HeadingHeight;

                this.MaxYPositions[i] = numRows * (this.ButtonSize + BUTTON_MARGIN) - scrollAreaHeight;

                if (this.MaxYPositions[i] < 0)
                    this.MaxYPositions[i] = 0;
            }
        }

        protected override void LoadContent()
        {
            this.LoadContent<SpriteFont>("LevelSelection/NumberFont", Content.NumberFont);
            this.LoadTexture("LevelSelection/Background", Content.Background);
            this.LoadTexture("LevelSelection/LevelOutline", Content.Button);
            this.LoadTexture("LevelSelection/LevelThumbnailOverlay", Content.ThumbnailOverlay);
            this.LoadTexture("LevelSelection/Heading", Content.Heading);
        }

        protected override void Update(double gameTime)
        {
            // switch to GameState with a LevelDifficulty and level number handy.
            //this.SwitchToState(typeof(GameState), LevelDifficulty.Beginner, 1);
            if (!this.DoneLoading)
            {
                this.Thumbnails[(int)HighestDifficultyLoaded][HighestLevelNumberLoaded - 1]
                    = this.GetThumbnail(HighestDifficultyLoaded, HighestLevelNumberLoaded);
                HighestLevelNumberLoaded++;
                if (HighestLevelNumberLoaded > Grid.GetNumLevelsOfDifficulty(HighestDifficultyLoaded))
                {
                    HighestLevelNumberLoaded = 1;
                    if (HighestDifficultyLoaded != LevelDifficulty.Master) HighestDifficultyLoaded++;
                    else this.DoneLoading = true;
                }
            }

            foreach (TouchLocation touch in Touch.Touches)
            {
                if (touch.State == TouchLocationState.Pressed || touch.State == TouchLocationState.Released)
                {
                    int indexTouched = this.IndexFromLoc(touch.Position);
                    if (indexTouched != -1)
                    {
                        // if a level was clicked, and then released, then switch to that level.
                        if (touch.State == TouchLocationState.Pressed)
                            this.LevelNumberTouched = indexTouched;
                        else if (touch.State == TouchLocationState.Released)
                        {
                            if (this.LevelNumberTouched == indexTouched)
                                this.SwitchToState(typeof(GameState), Grid.FromFile(this.CurrentDifficulty, indexTouched + 1));
                            this.LevelNumberTouched = -1;
                        }
                    }
                    else if (touch.Position.Y >= this.HeadingHeight)
                        // otherwise, and if the touch was not in the heading, stop any scrolling.
                        this.YVelocity = 0;
                }
            }

            foreach (GestureSample gesture in Touch.Gestures)
            {
                // GestureType.[Tap|DoubleTap|Flick|VerticalDrag]
                switch (gesture.GestureType)
                {
                    case GestureType.HorizontalDrag:
                    {
                        this.ScrollingHorizontally = true;
                        this.LevelNumberTouched = -1;
                        this.XOffset += gesture.Delta.X;
                        if (((int)this.CurrentDifficulty == 0 && this.XOffset > 0)
                            || (int)this.CurrentDifficulty == Difficulty.NumDifficulties - 1 && this.XOffset < 0)
                            this.XOffset = 0;
                        break;
                    }
                    case GestureType.Flick:
                    {
                        if (Math.Abs(gesture.Delta.Y) > Math.Abs(gesture.Delta.X))
                            this.YVelocity += gesture.Delta.Y;
                        else
                        {
                            if (gesture.Delta.X < 0 && CurrentDifficulty != LevelDifficulty.Master)
                                CurrentDifficulty++;
                            else if (gesture.Delta.X > 0 && CurrentDifficulty != LevelDifficulty.Beginner)
                                CurrentDifficulty--;
                        }
                        break;
                    }
                    case GestureType.VerticalDrag:
                    {
                        this.YPosition += gesture.Delta.Y;
                        break;
                    }
                    case GestureType.DragComplete:
                    {
                        if (this.ScrollingHorizontally)
                        {
                            this.ScrollingHorizontally = false;
                            if (this.XOffset <= Game.Width / 2f)
                                this.CurrentDifficulty++;
                            else if (this.XOffset >= Game.Width / 2f)
                                this.CurrentDifficulty--;
                            this.XOffset = 0;
                        }
                        break;
                    }
                }
            }
            this.YVelocity *= VELOCITY_MULTIPLIER;
            if (this.YVelocity <= 1f - VELOCITY_MULTIPLIER)
                this.YVelocity = 0;
            this.YPosition += this.YVelocity;

            if (this.YPosition < 0) this.YPosition = 0;
            if (this.YPosition > this.MaxYPositions[(int)this.CurrentDifficulty])
                this.YPosition = this.MaxYPositions[(int)this.CurrentDifficulty];

            base.Update(gameTime);
        }

        private Texture2D GetThumbnail(LevelDifficulty difficulty, int number)
        {
            Grid grid = Grid.FromFile(difficulty, number);
            RenderTarget2D renderTarget = new RenderTarget2D(this.GraphicsDevice,
                this.GetTexture(Content.ThumbnailOverlay).Width,
                this.GetTexture(Content.ThumbnailOverlay).Height);
            SpriteBatch g = new SpriteBatch(this.GraphicsDevice);

            this.GraphicsDevice.SetRenderTarget(renderTarget);
            grid.RenderToTarget(g, new Vector2(renderTarget.Width, renderTarget.Height), Color.Black);
            this.GraphicsDevice.SetRenderTarget(null);

            Texture2D thumbnail = renderTarget;
            Color[] thumbnailColors = new Color[thumbnail.Width * thumbnail.Height],
                    overlayColors = new Color[this.GetTexture(Content.ThumbnailOverlay).Width *
                                              this.GetTexture(Content.ThumbnailOverlay).Height];
            thumbnail.GetData<Color>(thumbnailColors);
            this.GetTexture(Content.ThumbnailOverlay).GetData<Color>(overlayColors);
            for (int i = 0; i < thumbnailColors.Length; i++)
            {
                thumbnailColors[i].A = (byte)(thumbnailColors[i].A * ((float)overlayColors[i].A / byte.MaxValue));
                thumbnailColors[i].R = (byte)(thumbnailColors[i].R * ((float)overlayColors[i].R / byte.MaxValue));
                thumbnailColors[i].G = (byte)(thumbnailColors[i].G * ((float)overlayColors[i].G / byte.MaxValue));
                thumbnailColors[i].B = (byte)(thumbnailColors[i].B * ((float)overlayColors[i].B / byte.MaxValue));
            }
            thumbnail.SetData<Color>(thumbnailColors);

            return thumbnail;
        }

        protected override void Draw(SpriteBatch g)
        {
            g.Draw(this.GetTexture(Content.Background), new Rectangle(0, 0, Game.Width, Game.Height), Color.White);
            g.Draw(this.GetTexture(Content.Heading), Vector2.Zero, Color.White);
            for (int difficulty = (int)this.CurrentDifficulty - 1;
                difficulty <= (int)this.CurrentDifficulty + 1 && difficulty >= 0 && difficulty < Difficulty.NumDifficulties;
                difficulty++)
            {
                for (int row = 0; row < this.Thumbnails[(int)this.CurrentDifficulty].Length; row++)
                {
                    for (int col = 0; col < NUM_COLUMNS &&
                        row * NUM_COLUMNS + col < Grid.GetNumLevelsOfDifficulty(this.CurrentDifficulty); col++)
                    {
                        Texture2D thumbnail = this.Thumbnails[difficulty][row * NUM_COLUMNS + col];
                        Vector2 thumbnailLoc = new Vector2(BUTTON_MARGIN + col * (this.ButtonSize + BUTTON_MARGIN),
                            this.HeadingHeight + BUTTON_MARGIN + row * (this.ButtonSize + BUTTON_MARGIN))
                            + new Vector2(difficulty - (int)this.CurrentDifficulty, 0) * Game.Width;
                        if (thumbnail != null)
                            g.Draw(thumbnail, thumbnailLoc + new Vector2(this.XOffset, 0)
                                + new Vector2(difficulty - (int)this.CurrentDifficulty, 0) * Game.Width,
                                Color.White);
                        g.Draw(this.GetTexture(Content.Button), thumbnailLoc + new Vector2(this.XOffset, 0)
                            + new Vector2(difficulty - (int)this.CurrentDifficulty, 0) * Game.Width,
                            Color.White);

                        string numberStr = (this.IndexFromXY(col, row) + 1).ToString();
                        Vector2 numberStrSize = this.GetContent<SpriteFont>(Content.NumberFont).MeasureString(numberStr);
                        Vector2 numberStrLoc = thumbnailLoc + (new Vector2(ButtonSize) - numberStrSize) / 2f
                            + new Vector2(this.XOffset, 0)
                            + new Vector2(difficulty - (int)this.CurrentDifficulty, 0) * Game.Width;
                        g.DrawString(this.GetContent<SpriteFont>(Content.NumberFont), numberStr, numberStrLoc, Color.White);
                    }
                }
            }
        }

        private int IndexFromXY(int x, int y)
        {
            return x + y * NUM_COLUMNS;
        }
        private Point XYFromIndex(int index)
        {
            return new Point(index % NUM_COLUMNS, index / NUM_COLUMNS);
        }
        private Point? XYFromLoc(Vector2 location)
        {
            if (location.Y < this.HeadingHeight)
                return null;

            int x = -1, y = -1;
            for (int col = 0; col < NUM_COLUMNS; col++)
            {
                if (location.X >= BUTTON_MARGIN + (this.ButtonSize + BUTTON_MARGIN) * col
                    && location.X < (this.ButtonSize + BUTTON_MARGIN) * (col + 1))
                {
                    x = col;
                    break;
                }
            }
            int lowestVisibleRow = -1;
            while (BUTTON_MARGIN + (this.ButtonSize + BUTTON_MARGIN) * ++lowestVisibleRow
                < this.YPosition - (Game.Height - this.HeadingHeight)) ;
            int highestVisibleRow = lowestVisibleRow;
            while (BUTTON_MARGIN + (this.ButtonSize + BUTTON_MARGIN) * ++highestVisibleRow
                < this.YPosition - (Game.Height - this.HeadingHeight)) ;

            for (int row = lowestVisibleRow; row <= highestVisibleRow; row++)
            {
                if (location.Y - this.HeadingHeight >= BUTTON_MARGIN + (this.ButtonSize + BUTTON_MARGIN) * row
                    && location.Y - this.HeadingHeight < (this.ButtonSize + BUTTON_MARGIN) * (row + 1))
                {
                    y = row;
                    break;
                }
            }

            if (x != -1 && y != -1)
                return new Point(x, y);
            else return null;
        }
        private int IndexFromLoc(Vector2 location)
        {
            Point? thumbnail = this.XYFromLoc(location);
            if (!thumbnail.HasValue) return -1;
            int index = this.IndexFromXY(thumbnail.Value.X, thumbnail.Value.Y);
            if (index < Grid.GetNumLevelsOfDifficulty(this.CurrentDifficulty))
                return index;
            return -1;
        }
    }
}
